package com.platform.qujia.utils;

import org.apache.commons.codec.binary.Base64;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.io.*;
import java.security.*;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Enumeration;

/**
 * <p> @Description:  </p>
 *
 * <p> @Company: www.mfhcd.com </p>
 *
 * @version V1.0
 * @author: roc.soon
 * @date 2019-10-22 下午 07:57
 */
public class RSAKeyUtil {

    public static final String RSA = "RSA";

    /**
     * 从文件中加载私钥
     *
     * @param filePath 文件目录
     * @param fileType 文件类型
     * @param password 文件密码
     * @return PrivateKey
     */
    static PrivateKey getPrivateKey(String filePath, String fileType, String password) throws Exception {

        PrivateKey key = null;
        InputStream inputStream = new FileInputStream(filePath);

        String keyType = "JKS";
        if ("keystore".equalsIgnoreCase(fileType)) {
            keyType = "JKS";
        } else if ("pfx".equalsIgnoreCase(fileType)) {
            keyType = "PKCS12";
        } else if ("pem".equalsIgnoreCase(fileType)) {
            keyType = "PEM";
        }

        try {
            if (!"PEM".equalsIgnoreCase(keyType)) {
                KeyStore ks = KeyStore.getInstance(keyType);
                char[] cPasswd = password.toCharArray();
                ks.load(inputStream, cPasswd);
                Enumeration<String> aliasenum = ks.aliases();
                String keyAlias;
                while (aliasenum.hasMoreElements()) {
                    keyAlias = aliasenum.nextElement();
                    key = (PrivateKey) ks.getKey(keyAlias, cPasswd);
                    if (key != null)
                        break;
                }
                return key;
            } else {
                return getPrivateKey(inputStream);
            }
        } catch (NoSuchAlgorithmException e) {
            throw new Exception("无此算法");
        } catch (InvalidKeySpecException e) {
            throw new Exception("公钥非法");
        } catch (IOException e) {
            throw new Exception("公钥数据内容读取错误");
        } catch (NullPointerException e) {
            throw new Exception("公钥数据为空");
        } finally {
            try {
                inputStream.close();
            } catch (Exception e) {
                throw new Exception("关闭输入流出错", e);
            }
        }
    }

    /**
     * 从文件中加载私钥
     *
     * @param inputStream InputStream
     * @return PrivateKey
     */
    private static PrivateKey getPrivateKey(InputStream inputStream) throws Exception {

        BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));
        try {
            String readLine;
            StringBuilder sb = new StringBuilder();
            while ((readLine = br.readLine()) != null) {
                if (readLine.charAt(0) != '-') {
                    sb.append(readLine);
                    sb.append('\r');
                }
//                if (readLine.charAt(0) == '-') {
//                    continue;
//                } else {
//                    sb.append(readLine);
//                    sb.append('\r');
//                }
            }
            return getPrivateKey(sb.toString());
        } catch (IOException e) {
            throw new Exception("私钥数据读取错误");
        } catch (NullPointerException e) {
            throw new Exception("私钥输入流为空");
        } finally {
            try {
                br.close();
            } catch (Exception e) {
                throw new Exception("关闭输入缓存流出错");
            }

            try {
                inputStream.close();
            } catch (Exception e) {
                throw new Exception("关闭输入流出错");
            }
        }
    }

    /**
     * 从字符串中加载私钥
     *
     * @param privateKeyStr 私钥字符串
     * @return PrivateKey
     */
    static PrivateKey getPrivateKey(String privateKeyStr) throws Exception {
        try {
            byte[] buffer = Base64.decodeBase64(privateKeyStr);
            PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(buffer);
            KeyFactory keyFactory = KeyFactory.getInstance(RSA);
            return keyFactory.generatePrivate(keySpec);
        } catch (NoSuchAlgorithmException e) {
            throw new Exception("无此算法");
        } catch (InvalidKeySpecException e) {
            throw new Exception("私钥非法");
        } catch (NullPointerException e) {
            throw new Exception("私钥数据为空");
        }
    }

    /**
     * 从字符串中加载公钥
     *
     * @param publicKeyStr 公钥字符串
     * @return PublicKey
     */
    static PublicKey getPublicKey(String publicKeyStr) {
        byte[] b = Base64.decodeBase64(publicKeyStr);
        CertificateFactory certificateFactory;
        ByteArrayInputStream inputStream = new ByteArrayInputStream(b);
        PublicKey vPublicKey = null;
        try {
            certificateFactory = CertificateFactory.getInstance("X.509");
            X509Certificate x509Certificate = (X509Certificate) certificateFactory.generateCertificate(inputStream);
            vPublicKey = x509Certificate.getPublicKey();
        } catch (Exception e) {
        } finally {
            try {
                inputStream.close();
            } catch (IOException e) {
            }
        }
        return vPublicKey;
    }

    /**
     * 从字符串中加载公钥
     *
     * @param publicKey 公钥字符串
     * @return PublicKey
     */
    static PublicKey getPublicKey(byte[] publicKey) {
        CertificateFactory certificateFactory;
        ByteArrayInputStream inputStream = new ByteArrayInputStream(publicKey);
        PublicKey vPublicKey = null;
        try {
            certificateFactory = CertificateFactory.getInstance("X.509");
            X509Certificate x509Certificate = (X509Certificate) certificateFactory.generateCertificate(inputStream);
            vPublicKey = x509Certificate.getPublicKey();
        } catch (Exception e) {
        } finally {
            try {
                inputStream.close();
            } catch (IOException e) {
            }
        }
        return vPublicKey;
    }

    /**
     * RSA加密
     *
     * @param plainBytes      明文字节数组
     * @param publicKey       公钥
     * @param keyLength       密钥bit长度
     * @param reserveSize     padding填充字节数，预留11字节
     * @param cipherAlgorithm 加解密算法，一般为RSA/ECB/PKCS1Padding
     * @return 加密后字节数组，不经base64编码
     */
    public static byte[] encrypt(byte[] plainBytes, PublicKey publicKey, int keyLength, int reserveSize, String cipherAlgorithm) throws Exception {

        int keyByteSize = keyLength / 8; // 密钥字节数
        int encryptBlockSize = keyByteSize - reserveSize; // 加密块大小=密钥字节数-padding填充字节数
        int nBlock = plainBytes.length / encryptBlockSize;// 计算分段加密的block数，向上取整
        if ((plainBytes.length % encryptBlockSize) != 0) { // 余数非0，block数再加1
            nBlock += 1;
        }

        try {
            Cipher cipher = Cipher.getInstance(cipherAlgorithm);
            cipher.init(Cipher.ENCRYPT_MODE, publicKey);

            // 输出buffer，大小为nBlock个keyByteSize
            ByteArrayOutputStream outbuf = new ByteArrayOutputStream(nBlock * keyByteSize);
            // 分段加密
            for (int offset = 0; offset < plainBytes.length; offset += encryptBlockSize) {
                int inputLen = plainBytes.length - offset;
                if (inputLen > encryptBlockSize) {
                    inputLen = encryptBlockSize;
                }
                // 得到分段加密结果
                byte[] encryptedBlock = cipher.doFinal(plainBytes, offset, inputLen);
                // 追加结果到输出buffer中
                outbuf.write(encryptedBlock);
            }

            outbuf.flush();
            outbuf.close();
            return outbuf.toByteArray();
        } catch (NoSuchAlgorithmException e) {
            throw new Exception(String.format("没有[%s]此类加密算法", cipherAlgorithm));
        } catch (NoSuchPaddingException e) {
            throw new Exception(String.format("没有[%s]此类填充模式", cipherAlgorithm));
        } catch (InvalidKeyException e) {
            throw new Exception("无效密钥");
        } catch (IllegalBlockSizeException e) {
            throw new Exception("加密块大小不合法");
        } catch (BadPaddingException e) {
            throw new Exception("错误填充模式");
        } catch (IOException e) {
            throw new Exception("字节输出流异常");
        }
    }

    /**
     * 将签名私钥证书文件读取为证书存储对象
     *
     * @param signCertPath 签名证书目录
     * @param signCertPwd  签名证书密码
     * @param signCertType 签名证书类型
     * @return 证书对象
     */
    static KeyStore getKeyInfo(String signCertPath, String signCertPwd, String signCertType) throws IOException {
        FileInputStream fis = null;
        try {
            if ("keystore".equalsIgnoreCase(signCertType)) {
                signCertType = "JKS";
            } else if ("pfx".equalsIgnoreCase(signCertType)) {
                signCertType = "PKCS12";
            } else if ("pem".equalsIgnoreCase(signCertType)) {
                signCertType = "PEM";
            }

            KeyStore keyStore = KeyStore.getInstance(signCertType);
            fis = new FileInputStream(signCertPath);
            char[] nPassword;
            nPassword = null == signCertPwd || "".equals(signCertPwd.trim()) ? null : signCertPwd.toCharArray();
            keyStore.load(fis, nPassword);
            return keyStore;
        } catch (Exception e) {
            return null;
        } finally {
            if (null != fis)
                fis.close();
        }
    }

}
